// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <algorithm>
#import <iterator>

#import "generator.h"

using std::ofstream;
using std::string;
using std::vector;

static const string in = "    ";
static const string inin = in + in;

static void write_syscall_signature_line(ofstream& os, const Syscall& sc, string name_prefix) {
  auto syscall_name = name_prefix + sc.name;

  os << "syscall_result " << syscall_name << "(";

  // Writes all arguments.
  sc.for_each_kernel_arg([&](const TypeSpec& arg) { os << arg.as_cpp_declaration(false) << ", "; });

  os << "uint64_t pc) {\n";
}

bool KernelWrapperGenerator::header(ofstream& os) {
  if (!Generator::header(os))
    return false;

  os << "extern \"C\" {\n";
  return os.good();
}

bool KernelWrapperGenerator::syscall(ofstream& os, const Syscall& sc) {
  if (sc.is_vdso())
    return true;

  auto syscall_name = syscall_prefix_ + sc.name;
  write_syscall_signature_line(os, sc, wrapper_prefix_);
  os << in << "return do_syscall(" << define_prefix_ << sc.name << ", "
     << "pc, "
     << "&VDso::ValidSyscallPC::" << sc.name << ", "
     << "[&](ProcessDispatcher* current_process) -> uint64_t {\n";

  string args;
  for (const TypeSpec& arg : sc.arg_spec) {
    if (!args.empty())
      args += ", ";
    if (arg.arr_spec) {
      args += "make_user_";
      args += arg.arr_spec->kind_lowercase_str();
      args += "_ptr(";
      args += arg.name;
      args += ")";
    } else {
      args += arg.name;
    }
  }

  vector<string> out_handles;

  sc.for_each_return([&](const TypeSpec& arg) {
    if (!args.empty())
      args += ", ";
    if (arg.arr_spec) {
      assert(arg.arr_spec->kind == ArraySpec::OUT);
      assert(arg.arr_spec->count == 1);
      if (arg.type == "zx_handle_t") {
        out_handles.push_back(arg.name);
        os << inin << "user_out_handle out_handle_" << arg.name << ";\n";
        args += "&out_handle_";
        args += arg.name;
      } else {
        args += "make_user_out_ptr(";
        args += arg.name;
        args += ")";
      }
    } else {
      args += arg.name;
    }
  });

  os << inin << (sc.is_noreturn() ? "/*noreturn*/ " : "auto result = ") << syscall_name << "("
     << args << ");\n";

  if (sc.is_noreturn()) {
    os << inin << "/* NOTREACHED */\n";
    os << inin << "return ZX_ERR_BAD_STATE;\n";
  } else {
    if (!out_handles.empty()) {
      // Don't try to copy handles to userspace on syscall error to avoid
      // leaking any handles that might have been set before failing.
      os << inin << "if (result != ZX_OK)\n";
      os << inin << in << "return result;\n";

      for (const auto& arg : out_handles) {
        os << inin << "if (out_handle_" << arg
           << ".begin_copyout(current_process, make_user_out_ptr(" << arg << ")))\n"
           << inin << in << "return ZX_ERR_INVALID_ARGS;\n";
      }
      for (const auto& arg : out_handles) {
        os << inin << "out_handle_" << arg << ".finish_copyout(current_process);\n";
      }
    }
    os << inin << "return result;\n";
  }

  os << in << "});\n"
     << "}\n";
  return os.good();
}

bool KernelWrapperGenerator::footer(ofstream& os) {
  os << "}\n";
  return os.good();
}
